Android拖动实现(一个流畅的拖动排序DragSortGridView,自动滚屏)

您所在的位置:网站首页 android studio长按删除 Android拖动实现(一个流畅的拖动排序DragSortGridView,自动滚屏)

Android拖动实现(一个流畅的拖动排序DragSortGridView,自动滚屏)

2023-10-28 05:57| 来源: 网络整理| 查看: 265

先上效果

这里写图片描述

流畅效果超越了网易新闻和UC浏览器的栏目收藏.gif图和实际效果有差距

1.拖拽可以移动item,并且其他item会立即自动补位,快速拖拽也非常流畅 2.item太多时,拖拽到边缘时会自动滚屏. 3.可以自定义被拖拽的的View放大,添加阴影等效果 4.长按启动删除模式,需要自己实现item的删除按钮展示,自己管理删除模式的切换 5.可以放在ScrollView中拖动排序,需要ScrollView继承ListenScrollView,不影响外面控件的大部分事件. 6.可以长按启动item拖拽,也可以触摸直接开始拖动

简单使用方法

dragSortGridView = (DragSortGridView) findViewById(R.id.dragSort1); //长按item响应该item的拖动排序,默认是触摸就开始拖动 dragSortGridView.setDragModel(DragSortGridView.DRAG_BY_LONG_CLICK); dragAdapter = new MyAdapter(); dragSortGridView.setAdapter(dragAdapter); 12345

dragAdapter 需要多实现onDataModelMove一个方法,界面排序改变需要提供真实数据排序改变.示例

class MyAdapter extends DragAdapter { @Override public void onDataModelMove(int from, int to) { String s = list.remove(from); list.add(to, s); } @Override public int getCount() { return list.size(); } @Override public String getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { TextView textView; if (convertView == null) { FrameLayout frameLayout = new FrameLayout(SecondActivity.this); convertView = frameLayout; textView = new TextView(SecondActivity.this); frameLayout.setPadding(20, 20, 20, 20); textView.setPadding(20, 100, 20, 100); frameLayout.addView(textView); textView.setBackgroundColor(0x33ff00ff); textView.setGravity(Gravity.CENTER); } else { textView = (TextView) ((FrameLayout) convertView).getChildAt(0); } textView.setText(getItem(position)); return convertView; } } 1234567891011121314151617181920212223242526272829303132333435363738394041

扩展可以改变的功能

//设置每行个数 dragSortGridView.setNumColumns(4); /*改变拖动item所在动画层,例如frameLayout是位于最上册的全屏透明层, 则item拖拽可以在全屏范围内,超出dragSortGridView本身范围, 这个一般用来配合外层是ListenScrollView用*/ dragSortGridView.setAnimFrame(frameLayout); //设置前面多少个位置固定,不能拖动 dragSortGridView.setNoPositionChangeItemCount(2); //设置尾部多少个位置固定,不能拖动 dragSortGridView.setFootNoPositionChangeItemCount(1); //修改item响应拖动时的效果,默认是放大到120% dragSortGridView.setOnDragSelectListener(new DragSortGridView.OnDragSelectListener() { @Override public void onDragSelect(View mirror) { //当item开始拖动时调用该方法 } @Override public void onPutDown(View itemView) { //当item被放时是调用该方法 } }); //修改长按拖动的响应时间 dragSortGridView.setDragLongPressTime(1500); dragSortGridView.setOnItemClickListener(...); dragSortGridView.setOnLongClickListener(...); 1234567891011121314151617181920212223242526272829

注意

不能给DragSortGridView设置padding,但是可以用margin,不能使用横竖间隙,要item之间的间距只能在adapter生成item里面设置padding必须做contentView的复用,不做则不流畅.

下面是源码 有一个R.id.first,需要在values的ids.xml里面添加

1 /** * Copyright (C), 2008-2015, Huawei Tech. Co., Ltd. *

* Description : 拖动排序布局 * * @version V100R001 * @since V100R001 */ @SuppressLint({ "NewApi", "Override" }) public class DragSortGridView extends FrameLayout { protected NoScrollGridView mGridView; private ScrollView mScrollView; private int headDragPosition = 0; private int footDragPosition = 0; private FrameLayout mDragFrame; private View mCopyView, hideView; private GestureDetector detector; /** 动画时间 */ private static final long ANIM_DURING = 250; protected int mNumColumns = 3, mColHeight = 0, mColWidth = 0, mChildCount = 0, mMaxHeight = 0; private int currentDragPosition = -1; private DragAdapter adapter; /** 持有子view */ private List mChilds = new ArrayList(); private static final int TAG_KEY = R.id.first; // private static final int TAG_KEY = R.id.tag_key; private int mCurrentY = 0; /** * 触摸区域,0不滚动区域,1可向上滚动的区域,-1可向下滚动的区域 */ private int mTouchArea = 0; /** * gridview能否滚动,是否内容太多 */ private boolean canScroll = true; /** * 是否可以拖动,点击拖动策略下直接开启,长按拖动需要长按以后开启 */ private boolean isDragable = true; /** * 自动滚屏的动画 */ private ValueAnimator animator; /** * view是否加载完成,如果未加载完成,没有宽高,无法接受事件 */ private boolean isViewInitDone = false; /** 是否有位置发生改变,否则不用重绘 */ private boolean hasPositionChange = false; /** 适配器的观察者,观察适配器的数据改变 */ private DataSetObserver observer = new DataSetObserver() { @Override public void onChanged() { mChildCount = adapter.getCount(); // 下列属性状态清除,才会在被调用notifyDataSetChange时,在gridview测量布局完成后重新获取 mChilds.clear(); mColHeight = mColWidth = mMaxHeight = 0; isViewInitDone = false; } @Override public void onInvalidated() { mChildCount = adapter.getCount(); } }; private float[] lastLocation = null; /** * 手势监听器,滚动和单击 */ private SimpleOnGestureListener simpleOnGestureListener = new SimpleOnGestureListener() { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (hasSendDragMsg) { hasSendDragMsg = false; handler.removeMessages(0x123); } if (isDragable && mCopyView != null) {// 可以拖动,实现跟随手指的拖动效果 // /// 2015/11/27补充修正跟随手指移动方法,适用于当本控件在drag时同时滚动的情况 if (lastLocation == null) { lastLocation = new float[] { e1.getRawX(), e1.getRawY() }; } distanceX = lastLocation[0] - e2.getRawX(); distanceY = lastLocation[1] - e2.getRawY(); lastLocation[0] = e2.getRawX(); lastLocation[1] = e2.getRawY(); // mCopyView.setX(mCopyView.getX() - distanceX); mCopyView.setY(mCopyView.getY() - distanceY); mCopyView.invalidate(); int to = eventToPosition(e2); if (to != currentDragPosition && to >= headDragPosition && to < mChildCount - footDragPosition) { onDragPositionChange(currentDragPosition, to); } } return true; } @Override public void onShowPress(MotionEvent e) { /** 响应长按拖拽 */ if (mDragMode == DRAG_BY_LONG_CLICK) { // 启动拖拽模式 // isDragable = true; // 通知父控件不拦截我的事件 getParent().requestDisallowInterceptTouchEvent(true); // 根据点击的位置生成该位置上的view镜像 int position = eventToPosition(e); if (position >= headDragPosition && position < mChildCount - footDragPosition) { // copyView(currentDragPosition = position); Message msg = handler.obtainMessage(0x123, position, 0); // showpress本身大概需要170毫秒 handler.sendMessageDelayed(msg, dragLongPressTime - 170); hasSendDragMsg = true; } } }; }; private boolean hasSendDragMsg = false; private Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { switch (msg.what) { case 0x123: // 启动拖拽模式 isDragable = true; // 根据点击的位置生成该位置上的view镜像 copyView(currentDragPosition = msg.arg1); hasSendDragMsg = false; break; default: break; } return false; } }); public DragSortGridView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public DragSortGridView(Context context) { super(context); init(); } private void init() { Context context = getContext(); mGridView = new NoScrollGridView(context); mGridView.setVerticalScrollBarEnabled(false); mGridView.setStretchMode(GridView.STRETCH_COLUMN_WIDTH); mGridView.setSelector(new ColorDrawable()); // View的宽高之类必须在测量,布局,绘制一系列过程之后才能获取到 mGridView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (mChilds.isEmpty()) { for (int i = 0; i < mGridView.getChildCount(); i++) { View view = mGridView.getChildAt(i); view.setTag(TAG_KEY, new int[] { 0, 0 }); view.clearAnimation(); mChilds.add(view); } } if (!mChilds.isEmpty()) { mColHeight = mChilds.get(0).getHeight(); } mColWidth = mGridView.getColumnWidth(); if (mChildCount % mNumColumns == 0) { mMaxHeight = mColHeight * mChildCount / mNumColumns; } else { mMaxHeight = mColHeight * (mChildCount / mNumColumns + 1); } canScroll = mMaxHeight - getHeight() > 0; // 告知事件处理,完成View加载,许多属性也已经初始化了 isViewInitDone = true; } }); mScrollView = new ListenScrollView(context); mDragFrame = new FrameLayout(context); addView(mScrollView, -1, -1); mScrollView.addView(mGridView, -1, -1); addView(mDragFrame, new LayoutParams(-1, -1)); detector = new GestureDetector(context, simpleOnGestureListener); detector.setIsLongpressEnabled(false); mGridView.setNumColumns(mNumColumns); } @Override public boolean onTouchEvent(MotionEvent ev) { if (l != null) { l.onTouch(this, ev); } if (!isViewInitDone) { return false; } if (isDragable) { handleScrollAndCreMirror(ev); } else { // 交给子控件自己处理 if (canScroll) mScrollView.dispatchTouchEvent(ev); else mGridView.dispatchTouchEvent(ev); } // 处理拖动 detector.onTouchEvent(ev); if (ev.getAction() == MotionEvent.ACTION_CANCEL || ev.getAction() == MotionEvent.ACTION_UP) { lastLocation = null; if (hasSendDragMsg) { hasSendDragMsg = false; handler.removeMessages(0x123); } } return true; } /** * Author :[pWX273343] 2015年7月22日 *

* Description :拦截所有事件 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } /** * 处理自动滚屏,和单击生成镜像 */ private void handleScrollAndCreMirror(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // 通知父控件不拦截我的事件 getParent().requestDisallowInterceptTouchEvent(true); // 根据点击的位置生成该位置上的view镜像 int position = eventToPosition(ev); if (position >= headDragPosition && position < mChildCount - footDragPosition) { copyView(currentDragPosition = position); } break; case MotionEvent.ACTION_MOVE: getParent().requestDisallowInterceptTouchEvent(true);// 通知父控件不拦截我的事件 // 内容太多时,移动到边缘会自动滚动 if (canScroll) { int touchArea = decodeTouchArea(ev); if (touchArea != mTouchArea) { onTouchAreaChange(touchArea); mTouchArea = touchArea; } } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (hideView != null) { hideView.setVisibility(View.VISIBLE); if (onDragSelectListener != null) { onDragSelectListener.onPutDown(hideView); } } mDragFrame.removeAllViews(); // mDragFrame.scrollTo(0, 0); // isNotifyByDragSort = true; if (hasPositionChange) { hasPositionChange = false; adapter.notifyDataSetChanged(); } else if (mDragMode == DRAG_BY_LONG_CLICK && itemLongClickListener != null) { itemLongClickListener.onItemLongClick(mGridView, childAt(currentDragPosition), currentDragPosition, 0); } // 停止滚动 if (canScroll) { int scrollStates2 = decodeTouchArea(ev); if (scrollStates2 != 0) { onTouchAreaChange(0); mTouchArea = 0; } } // 放手时取消拖动排序模式 if (mDragMode == DRAG_BY_LONG_CLICK) { isDragable = false; } break; default: break; } } /** * @param ev * 事件 * @return 0中间区域, 1底部,-1顶部 * @描述: 检查当前触摸事件位于哪个区域, 顶部1/5可能触发下滚,底部1/5可能触发上滚 * @作者 [pWX273343] 2015年6月30日 */ private int decodeTouchArea(MotionEvent ev) { if (ev.getY() > getHeight() * 4 / (double) 5) { return 1; } else if (ev.getY() < getHeight() / (double) 5) { return -1; } else { return 0; } } /** * @param ev * @return * @描述 得到事件触发点,摸到的是哪一个item * @作者 [pWX273343] 2015年7月6日 */ public int eventToPosition(MotionEvent ev) { if (ev != null) { int m = (int) ev.getX() / mColWidth; int n = (int) (ev.getY() + mCurrentY) / mColHeight; int position = n * mNumColumns + m; if (position >= mChildCount) { return mChildCount - 1; } else { return position; } } return 0; } // 这里把控件作为假的横向ListView,所以返回position跟高度无关,暂时这样 // public int eventToPosition(MotionEvent ev) { // // if (ev != null) { // int m = (int) ev.getX() / mColWidth; // if (m >= mChildCount) { // return mChildCount - 1; // } else { // return m; // } // } // return 0; // } /** * @param dragPosition * @描述:复制一个镜像,并添加到透明层 * @作者 [pWX273343] 2015年7月6日 */ private void copyView(int dragPosition) { hideView = mChilds.get(dragPosition); int realPosition = mGridView.indexOfChild(hideView); if (!adapter.isUseCopyView()) { mCopyView = adapter.getView(realPosition, mCopyView, mDragFrame); } else { mCopyView = adapter.copyView(realPosition, mCopyView, mDragFrame); } hideView.setVisibility(View.INVISIBLE); mDragFrame.addView(mCopyView, mColWidth, mColHeight); int[] l1 = new int[2]; int[] l2 = new int[2]; hideView.getLocationOnScreen(l1); mDragFrame.getLocationOnScreen(l2); // mCopyView.setX(hideView.getLeft()); // mCopyView.setY(hideView.getTop() - mCurrentY); mCopyView.setX(l1[0] - l2[0]); mCopyView.setY(l1[1] - l2[1]); if (onDragSelectListener == null) { mCopyView.setScaleX(1.2f); mCopyView.setScaleY(1.2f); } else { onDragSelectListener.onDragSelect(mCopyView); } } /** * @param from * @param to * @描述:动画效果移动View * @作者 [pWX273343] 2015年6月24日 */ private void translateView(int from, int to) { View view = mChilds.get(from); int fromXValue = ((int[]) view.getTag(TAG_KEY))[0]; int fromYValue = ((int[]) view.getTag(TAG_KEY))[1]; int toXValue = to % mNumColumns - from % mNumColumns + fromXValue; int toYValue = to / mNumColumns - from / mNumColumns + fromYValue; Animation animation = new TranslateAnimation(1, fromXValue, 1, toXValue, 1, fromYValue, 1, toYValue); animation.setDuration(ANIM_DURING); animation.setFillAfter(true); view.setTag(TAG_KEY, new int[] { toXValue, toYValue }); view.startAnimation(animation); } /** * @param from * @param to * @描述:拖动View使位置发生改变时 * @作者 [pWX273343] 2015年7月6日 */ private void onDragPositionChange(int from, int to) { if (from > to) { for (int i = to; i < from; i++) { translateView(i, i + 1); } } else { for (int i = to; i > from; i--) { translateView(i, i - 1); } } if (!hasPositionChange) { hasPositionChange = true; } adapter.onDataModelMove(from, to); View view = mChilds.remove(from); mChilds.add(to, view); currentDragPosition = to; } /** * Function :setAdapter *

* Author :[pWX273343] 2015年6月24日 *

* Description :设置适配器.该适配器必须实现一个方法,当view的位置发生变动时,对实际数据的改动 * * @param adapter * @see GridView#setAdapter(android.widget.ListAdapter) */ public void setAdapter(DragAdapter adapter) { if (this.adapter != null && observer != null) { this.adapter.unregisterDataSetObserver(observer); } this.adapter = adapter; mGridView.setAdapter(adapter); adapter.registerDataSetObserver(observer); mChildCount = adapter.getCount(); } public int getNumColumns() { return mNumColumns; } /** * 每行几个 */ public void setNumColumns(int numColumns) { this.mNumColumns = numColumns; mGridView.setNumColumns(numColumns); } /** * 设置前几个item不可以改变位置 */ public void setNoPositionChangeItemCount(int count) { headDragPosition = count; } /** * 设置后几个item不可以改变位置 */ public void setFootNoPositionChangeItemCount(int count) { footDragPosition = count; } /** * 控制自动滚屏的动画监听器. */ private AnimatorUpdateListener animUpdateListener = new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int targetY = Math.round((Float) animation.getAnimatedValue()); if (targetY < 0) { targetY = 0; } else if (targetY > mMaxHeight - getHeight()) { targetY = mMaxHeight - getHeight(); } // mGridView.scrollTo(0, targetY); mScrollView.smoothScrollTo(0, targetY); // mCurrentY = targetY; } }; /** * @param scrollStates * @描述:触摸区域改变,做相应处理,开始滚动或停止滚动 * @作者 [pWX273343] 2015年6月29日 */ protected void onTouchAreaChange(int scrollStates) { if (!canScroll) { return; } if (animator != null) { animator.removeUpdateListener(animUpdateListener); } if (scrollStates == 1) {// 从普通区域进入触发向上滚动的区域 int instance = mMaxHeight - getHeight() - mCurrentY; animator = ValueAnimator.ofFloat(mCurrentY, mMaxHeight - getHeight()); animator.setDuration((long) (instance / 0.5f)); animator.setTarget(mGridView); animator.addUpdateListener(animUpdateListener); animator.start(); } else if (scrollStates == -1) {// 进入触发向下滚动的区域 animator = ValueAnimator.ofFloat(mCurrentY, 0); animator.setDuration((long) (mCurrentY / 0.5f)); animator.setTarget(mGridView); animator.addUpdateListener(animUpdateListener); animator.start(); } } private OnDragSelectListener onDragSelectListener; /** * @描述:一个item view刚被拖拽和放下时起来生成镜像时调用. * @作者 [pWX273343] 2015年6月30日 */ public void setOnDragSelectListener(OnDragSelectListener onDragSelectListener) { this.onDragSelectListener = onDragSelectListener; } public interface OnDragSelectListener { /** * @param mirror * 所拖拽起来的view生成的镜像 ,并不是实际的view.可对这个镜像实施变换效果,但是并不改变放下后的效果 * @描述:拖拽起一个view时调用 * @作者 [pWX273343] 2015年6月30日 */ void onDragSelect(View mirror); /** * @param itemView * @描述:拖拽的View放下时调用 * @作者 [pWX273343] 2015年7月3日 */ void onPutDown(View itemView); } class NoScrollGridView extends GridView { public NoScrollGridView(Context context) { super(context); } /** * @return * @描述:兼容老版本的getColumWidth * @作者 [pWX273343] 2015年7月1日 */ public int getColumnWidth() { return getWidth() / getNumColumns(); } public NoScrollGridView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int mExpandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, mExpandSpec); } } /** * * Copyright (C), 2008-2015, Huawei Tech. Co., Ltd. *

* Description : 监听滚动的scrollview,我们需要实时知道他已滚动的距离 * * @author [pWX273343] 2015年7月22日 * @version V100R001 * @since V100R001 * */ class ListenScrollView extends ScrollView { public ListenScrollView(Context context) { super(context); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); mCurrentY = getScrollY(); } } public View getChildViewAtIndex(int index) { if (index < mChilds.size()) { return mChilds.get(index); } return null; } // 转交给gridview一些常用监听器 private OnItemLongClickListener itemLongClickListener; /** * * @描述:item 转交给gridview一些常用监听器 * * @param itemClickListener * @作者 [pWX273343] 2015年7月27日 */ public void setOnItemClickListener(OnItemClickListener itemClickListener) { mGridView.setOnItemClickListener(itemClickListener); } /** * 长按监听器自己触发,点击拖动模式不存在长按 * * @param */ public void setOnItemLongClickListener(OnItemLongClickListener itemLongClickListener) { this.itemLongClickListener = itemLongClickListener; } /** 点击拖动 */ public static final int DRAG_WHEN_TOUCH = 0; /** 长按拖动 */ public static final int DRAG_BY_LONG_CLICK = 1; private int mDragMode = DRAG_WHEN_TOUCH; /** * @param mode * int类型 * @描述:设置拖动的策略是点击还是长按 * @作者 [pWX273343] 2015年7月20日 参考 DRAG_WHEN_TOUCH,DRAG_BY_LONG_CLICK */ public void setDragModel(int mode) { this.mDragMode = mode; isDragable = mode == DRAG_WHEN_TOUCH; } public View childAt(int index) { return mGridView.getChildAt(index); } public int childCount() { return mGridView.getChildCount(); } public void setAnimFrame(FrameLayout mDragFrame) { this.mDragFrame = mDragFrame; } private OnTouchListener l; @Override public void setOnTouchListener(OnTouchListener l) { this.l = l; } private long dragLongPressTime = 600; /** * 设置长按需要用时 * * @param time */ public void setDragLongPressTime(long time) { dragLongPressTime = time; } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678

DragAdapter

public abstract class DragAdapter extends BaseAdapter { /** * * @描述:当从from排序被拖到to排序时的处理方式,请对相应的数据做处理。 * * @param from * @param to * @作者 [pWX273343] 2015年6月24日 */ public abstract void onDataModelMove(int from, int to); /** * 复制View使用的方法,默认直接使用getView方法获取 * @param position * @param convertView * @param parent * @return */ public View copyView(int position, View convertView, ViewGroup parent) { return null; } /** * 是否启用copyView方法 * @return true 使用copyView复制 false 使用getView直接获取镜像 */ public boolean isUseCopyView() { return false; } } 12345678910111213141516171819202122232425262728293031

ps: 其实相同功能的控件已经很多了,但是当时初学android写这个控件花了一些心血.或许也只能安慰地说一句,写这个控件的过程中了解了更多android的机制,也是一种成长经历吧.



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3